package com.jinmdz.fmis.api.api.service;

import com.jinmdz.fmis.api.api.model.calcrule.*;
import com.jinmdz.fmis.api.base.BaseService;
import com.jinmdz.fmis.api.wrapper.HandlerWrapper;
import com.jinmdz.fmis.common.util.DataUtil;
import com.jinmdz.fmis.common.util.StringUtil;
import com.jinmdz.fmis.core.base.BaseBean;
import com.jinmdz.fmis.core.base.BaseResult;
import com.jinmdz.fmis.core.constant.RuleConst;
import com.jinmdz.fmis.core.util.JacksonUtil;
import com.jinmdz.fmis.core.wrapper.ResultWrapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;

/**
 * CalcRuleService类
 *
 * @author LiCongLu
 * @date 2020-04-07 13:46
 */
@Service("calcRuleService")
public class CalcRuleService extends BaseService {

    @Resource
    private HandlerWrapper handlerWrapper;

    /**
     * 按照计算规则加载费用
     *
     * @param data 计算规则
     * @return
     * @author LiCongLu
     * @date 2020-04-07 13:55
     */
    public BaseResult<String> loadChargeWithRule(CalcRuleData data) {
        BaseResult result;

        String ruleData = data.getRuleData();

        // 判断分支
        switch (data.getRuleCode().toUpperCase()) {
            case RuleConst.A0100000:
                result = calcHandler(ruleData, A0100000Data.class, calcData -> calcA0100000(calcData));
                break;
            case RuleConst.A0200000:
                result = calcHandler(ruleData, A0200000Data.class, calcData -> calcA0200000(calcData));
                break;
            case RuleConst.B0200000:
                result = calcHandler(ruleData, B0200000Data.class, calcData -> calcB0200000(calcData));
                break;
            case RuleConst.C0200000:
                result = calcHandler(ruleData, C0200000Data.class, calcData -> calcC0200000(calcData));
                break;
            case RuleConst.D0100000:
                result = calcHandler(ruleData, D0100000Data.class, calcData -> calcD0100000(calcData));
                break;
            case RuleConst.E0100000:
                result = calcHandler(ruleData, E0100000Data.class, calcData -> calcE0100000(calcData));
                break;
            default:
                result = failure("无法识别计算编码");
                break;
        }

        return result;
    }

    /**
     * A0100000
     * A0100000编码表示，车辆按次收费，每次收取固定金额，接口将直接返回车辆单价。
     * eg: 车辆接运单价为80，则返回接运费为80。
     * 费用=行驶里程单价【itemPrice】
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:24
     */
    private BaseResult calcA0100000(A0100000Data calcData) {
        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量
        BigDecimal ruleNumber = new BigDecimal(1);
        // 金额
        BigDecimal itemCharge = itemPrice;

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("每次【{0}】元", DataUtil.getPlainString(itemCharge));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.A0100000)
                .setRuleUnit("次")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }

    /**
     * A0200000
     * A0200000编码表示，车辆按行驶里程收费，按行驶里程进行接运费用计算，接口返回计算后的车辆费用。
     * eg1: 普通殡仪车10公里以内130元，超出10公里，每公里加收3.5元。
     * eg2: 高档灵车30公里以内450元，超出30公里，每公里加收3.5元
     * <p>
     * 费用=((transportDistance>distanceRange)?(itemPrice+(transportDistance-distanceRange)*addPricePerKilometre):itemPrice)
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:24
     */
    private BaseResult calcA0200000(A0200000Data calcData) {
        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量
        BigDecimal ruleNumber = calcData.getTransportDistance();
        // 金额
        BigDecimal itemCharge = itemPrice;

        // 计算费用
        if (calcData.getTransportDistance().compareTo(calcData.getDistanceRange()) > 0) {
            itemCharge = DataUtil.reset2Scale(itemPrice.add((calcData.getTransportDistance().subtract(calcData.getDistanceRange()))
                    .multiply(calcData.getAddPricePerKilometre())));
        }

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("【{0}】公里以内【{1}】元，超出【{0}】公里，每公里加收【{2}】元"
                , DataUtil.getPlainString(calcData.getDistanceRange())
                , DataUtil.getPlainString(calcData.getItemPrice())
                , DataUtil.getPlainString(calcData.getAddPricePerKilometre()));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.A0200000)
                .setRuleUnit("公里")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }

    /**
     * B0200000
     * B0200000编码表示，每满24小时算一天，不足一天的按一天算
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:24
     */
    private BaseResult calcB0200000(B0200000Data calcData) {

        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量，这里为天数
        BigDecimal ruleNumber = new BigDecimal(1);

        // 计算天数
        if (DataUtil.noNullOrEmpty(calcData.getAppointmentInTime(), calcData.getAppointmentOutTime())) {
            if (calcData.getAppointmentInTime().after(calcData.getAppointmentOutTime())) {
                throw exception("预约入藏时间晚于预约出藏时间");
            }
            ruleNumber = getB0200000Number(calcData.getAppointmentInTime(), calcData.getAppointmentOutTime());
        }

        // 金额
        BigDecimal itemCharge = DataUtil.reset2Scale(itemPrice.multiply(ruleNumber));

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("按预约时间，每满24小时算一天，不足一天的按一天算，每天【{0}】元"
                , DataUtil.getPlainString(calcData.getItemPrice()));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.B0200000)
                .setRuleUnit("天")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }

    /**
     * 结算冷藏，规则：每满24小时算一天，不足一天的按一天算
     *
     * @param inTime  入藏时间
     * @param outTime 出藏时间
     * @return
     * @author LiCongLu
     * @date 2020-04-08 17:17
     */
    private BigDecimal getB0200000Number(Date inTime, Date outTime) {
        //每满24小时算一天，不足一天的按一天算
        long hours = Math.abs(outTime.getTime() - inTime.getTime()) / (1000 * 60 * 60);
        long days = hours / 24;
        long restHours = hours % 24;
        return new BigDecimal(days + (restHours > 0 ? 1 : 0));
    }

    /**
     * C0200000
     * C0200000编码表示，按预约时间，每满60分钟算一小时，不足一小时的按一小时算
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:25
     */
    private BaseResult calcC0200000(C0200000Data calcData) {

        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量，这里为天数
        BigDecimal ruleNumber = new BigDecimal(1);

        // 计算天数
        if (DataUtil.noNullOrEmpty(calcData.getAppointmentInTime(), calcData.getAppointmentOutTime())) {
            if (calcData.getAppointmentInTime().after(calcData.getAppointmentOutTime())) {
                throw exception("预约入厅时间晚于预约出厅时间");
            }
            ruleNumber = getC0200000Number(calcData.getAppointmentInTime(), calcData.getAppointmentOutTime());
        }

        // 金额
        BigDecimal itemCharge = DataUtil.reset2Scale(itemPrice.multiply(ruleNumber));

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("按预约时间，每满60分钟算一小时，不足一小时的按一小时算，每小时【{0}】元"
                , DataUtil.getPlainString(calcData.getItemPrice()));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.C0200000)
                .setRuleUnit("小时")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }

    /**
     * 结算守灵，规则：按预约时间，每满60分钟算一小时，不足一小时的按一小时算
     *
     * @param inTime  入厅时间
     * @param outTime 出厅时间
     * @return
     * @author LiCongLu
     * @date 2020-04-08 17:27
     */
    private BigDecimal getC0200000Number(Date inTime, Date outTime) {
        //按预约时间，每满60分钟算一小时，不足一小时的按一小时算
        long minutes = Math.abs(outTime.getTime() - inTime.getTime()) / (1000 * 60);
        long hours = minutes / 60;
        long restMinutes = minutes % 60;
        return new BigDecimal(hours + (restMinutes > 0 ? 1 : 0));
    }

    /**
     * D0100000
     * D0100000编码表示，每场收取固定金额
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:25
     */
    private BaseResult calcD0100000(D0100000Data calcData) {
        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量
        BigDecimal ruleNumber = new BigDecimal(1);
        // 金额
        BigDecimal itemCharge = itemPrice;

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("每场【{0}】元", DataUtil.getPlainString(itemCharge));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.D0100000)
                .setRuleUnit("场")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }

    /**
     * E0100000
     * E0100000编码表示，每炉收取固定金额
     *
     * @param calcData 计算数据
     * @return
     * @author LiCongLu
     * @date 2020-04-07 14:25
     */
    private BaseResult calcE0100000(E0100000Data calcData) {
        // 价格
        BigDecimal itemPrice = calcData.getItemPrice();
        // 数量
        BigDecimal ruleNumber = new BigDecimal(1);
        // 金额
        BigDecimal itemCharge = itemPrice;

        // 计算返回结果
        CalcRuleLoadData loadData = new CalcRuleLoadData();
        // 备注
        String ruleRemark = StringUtil.format("每炉【{0}】元", DataUtil.getPlainString(itemCharge));

        // 设置规则数据
        loadData.setRuleCode(RuleConst.D0100000)
                .setRuleUnit("炉")
                .setRuleRemark(ruleRemark);

        // 设置费用
        loadData.setItemPrice(itemPrice)
                .setRuleNumber(ruleNumber)
                .setItemCharge(itemCharge);

        return successData(loadData);
    }


    /**
     * 计算请求
     *
     * @param ruleData 规则请求数据
     * @param clazz    规则请求数据类型
     * @param listener 监听
     * @return
     * @author LiCongLu
     * @date 2020-04-07 16:06
     */
    private <T extends BaseBean> BaseResult<String> calcHandler(String ruleData, Class<T> clazz, OnCalcListener<T> listener) {

        // 大括号
        String brace = "{}";

        // 判断计算数据
        if (DataUtil.invalid(ruleData) || brace.equals(ruleData)) {
            return failure("计算规则参数数据为空");
        }

        // 转化请求实体
        T data = JacksonUtil.json2Obj(ruleData, clazz);
        if (DataUtil.isNull(data)) {
            return failure("计算规则参数数据格式不正确");
        }

        // 需要验证请求数据的，请求数据不可为空
        if (data == null) {
            return ResultWrapper.failure("请求数据为空");
        }

        // 验证请求数据
        BaseResult result = handlerWrapper.validateBean(data);
        if (result != null) {
            return result;
        }

        // 判断是否存在处理监听
        if (listener == null) {
            return ResultWrapper.failure("未执行处理操作");
        }
        // 处理执行
        return listener.onCalc(data);
    }

    /**
     * OnRuleListener
     *
     * @author LiCongLu
     * @date 2020-04-07 16:01
     */
    @FunctionalInterface
    public interface OnCalcListener<T> {
        /**
         * 实际执行Rule
         *
         * @param data
         * @return
         * @author LiCongLu
         * @date 2020-04-07 16:02
         */
        BaseResult onCalc(T data);
    }
}